package ch.ethz.fcl.metrobuzz.data.interchange;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.roadnetwork.RoadNetwork;
import ch.ethz.fcl.metrobuzz.data.roadnetwork.hierarchy.Arc;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.singapore.SGInterchange;
import ch.ethz.fcl.metrobuzz.data.singapore.SGMRTStations;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorTable;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Rectangle;
import ch.ethz.fcl.metrobuzz.tools.MBTools;
import ch.ethz.fcl.metrofont.MetroFont;

public class Junction extends Interchange {
	private final float ARCTHRE = 0.01f;

	private List<Node> nodes;

	// connected junctions and their corresponding edges
	private Map<Junction, List<Edge>> junctionEdgeMap;
	private Map<Edge, Integer> edgeColorMap = new HashMap<Edge, Integer>();

	// group edges according to their colors
	private Map<Integer, ArrayList<Edge>> colorEdgeMap;

	public Junction(int index) {
		nodes = new ArrayList<Node>();
		this.index = index;
	}

	public void init(MBData data, SGInterchange sr, RoadNetwork rn) {
		initCircos(sr, rn);
		initEdges(sr, rn);
		initConnectedInterchange();
	}

	public void initCircos(SGInterchange sr, RoadNetwork rn) {
		Object[] colors = colorEdgeMap.keySet().toArray();
		matrixSize = colors.length + 1;

		linkDirs = new Vector2d[matrixSize - 1];
		for (int i = 0; i < matrixSize - 1; i++) {
			int color = (Integer) (colors[i]);
			linkDirs[i] = new Vector2d(0, 0);
			ArrayList<Edge> edges = colorEdgeMap.get(color);
			for (Edge edge : edges) {
				Vector2d dir = getDir(sr, rn, edge);
				linkDirs[i] = new Vector2d(linkDirs[i].x + dir.x, linkDirs[i].y
						+ dir.y);
			}
		}
	}

	// connected junctions from junction edge map
	private void initConnectedInterchange() {
		connectedInterchanges = new ArrayList<Interchange>();
		for (Junction j : junctionEdgeMap.keySet())
			connectedInterchanges.add(j);
	}

	public void addNode(Node node) {
		if (!nodes.contains(node))
			nodes.add(node);
	}

	public void addEdge(Edge e, boolean incoming) {
		if (incoming) {
			if (incomingEdges == null)
				incomingEdges = new ArrayList<Edge>();
			incomingEdges.add(e);
		} else {
			if (outgoingEdges == null)
				outgoingEdges = new ArrayList<Edge>();
			outgoingEdges.add(e);
		}
	}

	public void initEdges(SGInterchange sr, RoadNetwork rn) {
		incomingEdges = new ArrayList<Edge>();
		outgoingEdges = new ArrayList<Edge>();

		junctionEdgeMap = new HashMap<Junction, List<Edge>>();

		for (Node node : nodes) {
			List<Edge> incoming = node.getInEdges();
			for (Edge edge : incoming) {
				Arc arc = rn.getRoadHierarchy().getArc(edge);
				Node start = arc.getStartNode();
				Junction j = sr.getJunction(start);
				if (j != null) {
					if (j.getIndex() != index
							|| (j.getIndex() == index && arc.getLength() > ARCTHRE)) {
						incomingEdges.add(edge);
						addJunctionEdgeMap(j, edge);
					}
				}
			}

			List<Edge> outgoing = node.getOutEdges();
			for (Edge edge : outgoing) {
				Arc arc = rn.getRoadHierarchy().getArc(edge);
				Node end = arc.getEndNode();
				Junction j = sr.getJunction(end);
				if (j != null) {
					if (j.getIndex() != index
							|| (j.getIndex() == index && arc.getLength() > ARCTHRE)) {
						outgoingEdges.add(edge);
						addJunctionEdgeMap(j, edge);
					}
				}
			}
		}
	}

	private void addJunctionEdgeMap(Junction j, Edge e) {
		List<Edge> edges = junctionEdgeMap.get(j);
		if (edges == null) {
			edges = new ArrayList<Edge>();
			edges.add(e);
			junctionEdgeMap.put(j, edges);
		} else
			edges.add(e);
	}

	public void initEdgesColor(SGInterchange sr, RoadNetwork rn) {
		if (incomingEdges == null || outgoingEdges == null)
			return;
		for (int i = 0; i < incomingEdges.size(); i++) {
			Edge edge = incomingEdges.get(i);
			if (edgeColorMap.get(edge) != null)
				continue;
			Arc arc = rn.getRoadHierarchy().getArc(edge);
			Junction j = sr.getJunction(arc.getStartNode());
			int existColor = checkExist(sr, rn, j, edge);
			if (existColor != -1) {
				setColorMap(edge, existColor);
				j.setColorMap(arc.getStartEdge(), existColor);
			} else if (j.getIndex() != index) {
				int color = InterchangeColor.pickColorRange(this, j);
				setColorMap(edge, color);
				j.setColorMap(arc.getStartEdge(), color);
			} else if (j.getIndex() == index) {
				int color = InterchangeColor.pickColorRange(this, null);
				setColorMap(edge, color);
			}
		}

		for (int i = 0; i < outgoingEdges.size(); i++) {
			Edge edge = outgoingEdges.get(i);
			if (edgeColorMap.get(edge) != null)
				continue;
			Arc arc = rn.getRoadHierarchy().getArc(edge);
			Junction j = sr.getJunction(arc.getEndNode());
			int existColor = checkExist(sr, rn, j, edge);
			if (existColor != -1) {
				setColorMap(edge, existColor);
				j.setColorMap(arc.getEndEdge(), existColor);
			} else if (j.getIndex() != index) {
				int color = InterchangeColor.pickColorRange(this, j);
				setColorMap(edge, color);
				j.setColorMap(arc.getEndEdge(), color);
			} else if (j.getIndex() == index) {
				int color = InterchangeColor.pickColorRange(this, null);
				setColorMap(edge, color);
			}
		}
	}

	/**
	 * group edges together by examining whether they are connecting the same
	 * pair of junctions
	 */
	public void groupEdges() {
		colorEdgeMap = new HashMap<Integer, ArrayList<Edge>>();
		for (Edge edge : edgeColorMap.keySet()) {
			int color = edgeColorMap.get(edge);
			ArrayList<Edge> edges = colorEdgeMap.get(color);
			if (edges == null) {
				edges = new ArrayList<Edge>();
				edges.add(edge);
				colorEdgeMap.put(color, edges);
			} else
				edges.add(edge);
		}
	}

	/**
	 * get circos direction of a combination of edges
	 * 
	 * @param sr
	 * @param rn
	 * @param edge
	 * @return
	 */
	private Vector2d getDir(SGInterchange sr, RoadNetwork rn, Edge edge) {
		Point2D p = null;
		if (outgoingEdges != null && outgoingEdges.contains(edge)) {
			Arc arc = rn.getRoadHierarchy().getArc(edge);
			int index = 0;
			float dist = 0;
			while (index < arc.size() - 1
					&& dist + arc.getEdges().get(index).length() < circosRadius) {
				dist += arc.getEdges().get(index).length();
				index++;
			}
			Edge e = arc.getEdges().get(index);
			p = e.getStartNode().toPoint()
					.getPointAt(e.getDir(), circosRadius - dist);
		} else if (incomingEdges != null && incomingEdges.contains(edge)) {
			Arc arc = rn.getRoadHierarchy().getArc(edge);
			int index = arc.size() - 1;
			float dist = 0;
			while (index > 0
					&& dist + arc.getEdges().get(index).length() < circosRadius) {
				dist += arc.getEdges().get(index).length();
				index--;
			}
			Edge e = arc.getEdges().get(index);
			p = e.getEndNode()
					.toPoint()
					.getPointAt(MBTools.rotate(e.getDir(), Math.PI),
							circosRadius - dist);
		}

		if (p != null)
			return new Vector2d(p.getX() - getCircosCentroid().getX(), p.getY()
					- getCircosCentroid().getY());
		else
			return new Vector2d(0, 0);
	}

	public void initCircos() {
		Object[] colorSet = colorEdgeMap.keySet().toArray();
		linkColors = new int[colorSet.length];
		for (int i = 0; i < linkColors.length; i++)
			linkColors[i] = (Integer) colorSet[i];

		super.initCircos();
	}

	private int checkExist(SGInterchange sr, RoadNetwork rn, Junction j, Edge e) {
		List<Edge> between = junctionEdgeMap.get(j);
		if (between != null) {
			for (Edge edge : between) {
				Integer color = edgeColorMap.get(edge);
				if (color != null && !edge.getId().equals(e.getId())) {
					Arc e1Arc = rn.getRoadHierarchy().getArc(e);
					Arc e2Arc = rn.getRoadHierarchy().getArc(edge);

					float arcLengthDiff = Math.abs(e1Arc.getLength()
							- e2Arc.getLength());
					float arcLengthSum = Math.abs(e1Arc.getLength()
							+ e2Arc.getLength());

					Vector2d dir1 = new Vector2d(e1Arc.getMiddlePoint().getX()
							- getCircosCentroid().getX(), e1Arc
							.getMiddlePoint().getY()
							- getCircosCentroid().getY());
					Vector2d dir2 = new Vector2d(e2Arc.getMiddlePoint().getX()
							- getCircosCentroid().getX(), e2Arc
							.getMiddlePoint().getY()
							- getCircosCentroid().getY());
					float angle = (float) Math.abs(MBTools.angle(dir1, dir2));
					if (arcLengthDiff / arcLengthSum < 0.1f
							&& (angle < Math.PI / 6 || angle > Math.PI * 5 / 6)
							&& e1Arc.getIndex() != e2Arc.getIndex())
						return color;
				}
			}
		}
		return -1;
	}

	public void renderEdges(SGInterchange sr, RoadNetwork rn) {
		GL11.glLineWidth(2.0f);
		GL11.glBegin(GL11.GL_LINES);
		renderEdge(incomingEdges, rn);
		renderEdge(outgoingEdges, rn);
		GL11.glEnd();
	}

	private void renderEdge(List<Edge> edges, RoadNetwork rn) {
		if (edges == null)
			return;
		for (Edge e : edges) {
			Integer color = edgeColorMap.get(e);
			if (color != null) {
				MBColorTable.assignColor(color, 0.7f);
			} else
				MBColorTable.assignColor(MBColorTable.LIGHTGREY, 0.7f);
			Arc arc = rn.getRoadHierarchy().getArc(e);
			if (arc.getStartNode().equals(arc.getEndNode())) {
				if (nodes.contains(e.getStartNode()))
					for (int i = 0; i < arc.size() / 2; i++)
						arc.getEdges().get(i).render();
				else if (nodes.contains(e.getEndNode()))
					for (int i = arc.size() / 2; i < arc.size(); i++)
						arc.getEdges().get(i).render();
			} else
				for (Edge arcEdge : arc.getEdges())
					arcEdge.render();
		}
	}

	@Override
	public void render(Rectangle bound, MetroFont fm) {
		if (!bound.contains(getCircosCentroid()))
			return;

		if (cd != null)
			cd.render();
	}

	public void setColorMap(Edge edge, int color) {
		if (!edgeColorMap.containsKey(edge)) {
			edgeColorMap.put(edge, color);
		}

		if (colorEdgeMap == null)
			colorEdgeMap = new HashMap<Integer, ArrayList<Edge>>();

		ArrayList<Edge> edges = colorEdgeMap.get(color);
		if (edges == null) {
			edges = new ArrayList<Edge>();
			edges.add(edge);
			colorEdgeMap.put(color, edges);
		} else
			edges.add(edge);
	}

	public ArrayList<Integer> getArcElementColors() {
		ArrayList<Integer> colors = new ArrayList<Integer>();
		for (Edge edge : incomingEdges) {
			Integer c = edgeColorMap.get(edge);
			if (c != null)
				colors.add(c);
		}
		for (Edge edge : outgoingEdges) {
			Integer c = edgeColorMap.get(edge);
			if (c != null)
				colors.add(c);
		}
		return colors;
	}

	public List<Node> getNodes() {
		return nodes;
	}

	public Point2D getCircosCentroid() {
		if (circosCentroid == null) {
			float x = 0, y = 0;
			for (Node n : nodes) {
				x += n.getX();
				y += n.getY();
			}
			circosCentroid = new Point2D(x / nodes.size(), y / nodes.size());
		}
		return circosCentroid;
	}

	public boolean isSubway() {
		if (SGMRTStations.isMRTStation(nodes.get(0).getId()))
			return true;
		else
			return false;
	}

	public boolean isMRTNode(String mrt) {
		return SGMRTStations.isMRTLineStation(nodes.get(0).getId(),
				mrt.toUpperCase());
	}

	public Map<Integer, ArrayList<Edge>> getColorEdgesMap() {
		return colorEdgeMap;
	}
}
